package cz.data.auth.config;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import cz.data.auth.security.*;
import cz.data.auth.service.DataUserDetailService;
import cz.data.auth.service.SecurityService;
import cz.data.common.annotation.DataInner;
import cz.data.common.feign.interceptor.DataInnerRequestInterceptor;
import cz.data.common.redis.service.RedisService;
import cz.data.common.support.security.AnnotationMatcherRule;
import cz.data.common.support.security.DynamicRequestMatcherContainer;
import cz.data.common.support.security.DiscoveryServiceHeaderFactory;
import cz.data.common.support.security.DynamicRequestMatcherRule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.FileCopyUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.*;

@Configuration
public class SecurityConfig {

    @Resource
    RedisService redisService;

    @Order(1)
    @Bean
    public SecurityFilterChain oauthSecurityFilterChain(HttpSecurity http, JwtEncoder jwtEncoder) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(SessionManagementConfigurer::disable)
                .requestMatchers(request ->
                        request.mvcMatchers(HttpMethod.POST, "/oauth/login")
                )
                .authorizeRequests(request ->
                        request.anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .formLogin(login ->
                        login.loginProcessingUrl("/oauth/login")
                                .successHandler(new LoginAuthenticationSuccessHandler(jwtEncoder, redisService))
                                .failureHandler(new DefaultAuthenticationFailureHandler())
                )
                .logout(LogoutConfigurer::disable)
                .userDetailsService(userDetailsService())
                .build();
    }

    @Order(2)
    @Bean
    @ConditionalOnBean(DynamicRequestMatcherContainer.class)
    public SecurityFilterChain dynamicRequestMatcherSecurityFilterChain(
            HttpSecurity http, DynamicRequestMatcherContainer container, SecurityService securityService) throws Exception {
        List<DynamicRequestMatcherRule> requestMatchers = container.requestMatchers();
        requestMatchers.add(new AnnotationMatcherRule(HttpMethod.GET, new String[]{"/favicon.ico"}, DataInner.Access.permitAll));
        return http
                .requestMatchers(request ->
                        requestMatchers.forEach(requestMatcher -> request.mvcMatchers(requestMatcher.method(), requestMatcher.patterns()))
                )
                .authorizeRequests(request ->
                        requestMatchers.forEach(matcher ->
                                request.mvcMatchers(matcher.method(), matcher.patterns()).access(matcher.access())
                        )
                )
                .csrf(CsrfConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterAfter(new NacosServiceAuthenticationFilter(securityService), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean
    @Order(4)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {
        return http
                .authorizeHttpRequests((authorize) ->
                        authorize.anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .formLogin(login -> login.loginProcessingUrl("/oauth/login"))
                .csrf(CsrfConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }

    @Order(2)
    @Bean
    @ConditionalOnBean(DiscoveryServiceHeaderFactory.class)
    public DataInnerRequestInterceptor dataInnerRequestInterceptor(DiscoveryServiceHeaderFactory factory) {
        return new DataInnerRequestInterceptor(factory);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new DataUserDetailService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
        jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
        jwsAlgs.addAll(JWSAlgorithm.Family.EC);
        jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
        ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
        JWSKeySelector<SecurityContext> jwsKeySelector = new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
        jwtProcessor.setJWSKeySelector(jwsKeySelector);
        jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
        });
        NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor);
        jwtDecoder.setJwtValidator(token -> OAuth2TokenValidatorResult.success());
        return jwtDecoder;
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        return new NimbusJwtEncoder(jwkSource());
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        JWKSet jwkSet;
        try {
            ClassPathResource resource = new ClassPathResource("jwk.json");
            InputStreamReader reader = new InputStreamReader(resource.getInputStream());
            jwkSet = JWKSet.parse(FileCopyUtils.copyToString(reader));
        } catch (ParseException | IOException e) {
            throw new RuntimeException(e);
        }
        return new ImmutableJWKSet<>(jwkSet);
    }

}
